;** Midi centisecond code **

CentisecondCode ROUT
; This is now called from SoundDMA. It intercepts the normal call to SoundScheduler
; from there. It gets called once every SoundIRQ or approx 100 times per second
     STMFD   sp!, {r0-r4,r9,lr}
     LDR     r2, [R12, #ModeFlagBits]
; r2 = Mode flags
; ensure sound scheduler is present
     TST     r2, #Sound2Present
     BEQ     %FT10

     TST     r2, #ExternalCount
     TSTNE   r2, #SynchSoundScheduler ; is sound scheduler external synch wanted?
     BNE     %FT10

; dispatch sound scheduler as normal
     MOV     r1, r12 ; save wp
     LDR     r0, [r12, #Sound_QTick]
 ; ensure no recursive re-entry of this routine
     CMP     r0, r12
      MOVEQ   r0, #SoundSystemNIL   ; ensure the next test fails with NE
      STREQ   r0, [r12, #Sound_QTick]
     TST     r0, #SoundSystemNIL   ; test for installed scheduler
     MOVEQ   r12, r0               ; wp
     LDREQ   r0, [r12]
     TSTEQ   r0, #SoundSystemNIL     ; Valid Level2?
     MOVEQ   lr, pc
     MOVEQ   pc, r0                  ; Call Level2
     MOV     r12, r1                 ; restore r12

;change to svc mode and save lr so that it can be restored at the end
; in case it was interrupted from an svc-mode routine, since I make swi
; calls which will corrupt lr_svc
10   TEQP    pc, #I_bit:OR:3    ; change to svc mode (mode3) retaining irq off
     MOVNV   r0, r0    ; delay for regbank to change
     STMFD   sp!, {lr}  ; stack lr_svc value

; generate an event

; A. if BufferFilling flag has been set by the irq routine
     MOV     r0, #Event_MIDI
     TST     r2, #BufferFilling
     BIC     r2, r2, #BufferFilling ; clear the flag
     MOVNE   r1, #MIDI_DataReceivedEvent
     STRNE   r2, [r12, #ModeFlagBits] ; in case the SWI enables irqs
     SWINE   XOS_GenerateEvent
     LDRNE   r2, [r12, #ModeFlagBits] ; in case the SWI enables irqs

; B. if Error flag has been set by the irq routine
     TST     r2, #BackgroundError
     BIC     r2, r2, #BackgroundError ; clear the flag
     MOVNE   r1, #MIDI_ErrorEvent
     STRNE   r2, [r12, #ModeFlagBits] ; in case the SWI enables irqs
     SWINE   XOS_GenerateEvent
     LDRNE   r2, [r12, #ModeFlagBits] ; in case the SWI enables irqs

;C; if scheduler queue is 'nearly' empty
; main test is for less than (10ms) of data in scheduler. But check for
; silly cases, such as empty, or less than 4 free slots in scheduler
     TST     r2, #Version3Facilities
     BEQ     %FT110
     TST     r2, #WarnedEmptyScheduler
     BNE     %FT110
     LDR     r1, [r12, #Slot_p]
     MOV     r4, r1, LSR#16      ; top 16b = next free slot
     BIC     r1, r1, #&FF000000
     BIC     r1, r1, #&00FF0000  ;  bottom 16b = next slot to empty
     SUBS    r1, r1, r4    ; slots free
     BEQ     %FT110              ; if empty do nothing
     ADDMI   r1, r1, #MaxSchedulerSlots  ; add buffer length if negative (circular buffer)
     CMP     r1, #4   ; ensure there are at least 4 slots free before saying it is nearly empty
     BLT     %FT110
    ; get time of last event in scheduler
     SUBS    r4, r4, #1         ; previous slot (last scheduled)
     ADDMI   r4, r4,  #MaxSchedulerSlots ; wrap buffer
     ADD     r3, r12, #SchedulerSpace ; start of scheduler buffer
     ADD     r3, r3, r4, LSL#3  ; last scheduled slot
     LDR     r3, [r3, #ScheduleTime]   ; get schedule time
     TST     r2, #FastClock
     LDRNE   r4, [r12, #ms_count]       ; get current time
     LDREQ   r4, [r12, #SongPosPointer]
     SUBS    r3, r3, r4     ; time of last scheduled event from current time
     BMI     %FT110         ; some sort of error? Later than current time
     CMP     r3, #QEmptyWarningTime
     ; generate event. r0 is set to event_midi
     MOVLE   r1, #MIDI_SchedulerEmptyingEvent
     ORRLE   r2, r2, #WarnedEmptyScheduler
     STRLE   r2, [r12, #ModeFlagBits] ; in case the SWI enables irqs
     SWILE   XOS_GenerateEvent
     LDRLE   r2, [r12, #ModeFlagBits] ; in case the SWI enables irqs

110  LDRB    r0, [r12, #TimeCount]  ;how many cs have passed since last update?
; r0 = TimeCount
     SUBS    r0, r0, #1
     MOVMI   r0, #SlowClock
     STRB    r0, [r12, #TimeCount]
     BLMI    DoRareUpdate
     BLVS    SoundSWIError     ; if sound system returned V set = error
     BVS     %FT40

 ; get current value of sound beat counter
     MOV     r0, #0
     TST     r2, #FastClock
     BNE     %FT45   ; don't bother about beat count if using fastclock
     SWI     XSound_QBeats
     BLVS    SoundSWIError ; abort if v set
     BVS     %FT40

; data timing. Must choose between internal and external timing
     LDR     r1, [r12, #CurrentQBeat]
; r0 = beat
; r1 = current q beat
     MOV     r1, r1, LSR#16 ; clear bottom 16 bits (beat count)
     ORR     r1, r0, r1, LSL#16      ; update beat count
     STR     r1, [r12, #CurrentQBeat]
; set or unset external timing mode if requested from irq routine (if Real Time message received)
45   TST     r2, #SetExternalC
; set external timing mode
     ORRNE   r2, r2, #ExternalCount  ; irq is off!
     BICNE   r2, r2, #InternalCount
     BEQ     %FT15
; stop fast clock for external count
     TST     r2, #FastClock
     ORRNE   r2, r2, #WasFastClock    ; show that this was auto-disabled for the duration of the ext count
     MOVS    r0, #0  ; set z flag to release
     STR     r2, [r12, #ModeFlagBits]    ; irq is off!
     BL      ClaimReleaseTimer ; enter with z set to release; ignore error
     LDR     r2, [r12, #ModeFlagBits]    ; irq is off!

15   TST     r2, #ClearExternalC
     BICNE   r2, r2, #ExternalCount
     TSTNE   r2, #WasFastClock      ; auto-restart fastclock
     BEQ     %FT16
; restart fast clock for internal count
     BIC     r2, r2, #WasFastClock
     MOVS    r0, #1   ; clear z flag to claim
     STRNE   r2, [r12, #ModeFlagBits]    ; irq is off!
     BLNE    ClaimReleaseTimer ; enter with z clear to claim; ignore error
     LDREQ   r2, [r12, #ModeFlagBits]    ; irq is off!

; clear instruction
16   BIC     r2, r2, #SetExternalC:OR:ClearExternalC
     STR     r2, [r12, #ModeFlagBits]    ; irq is off!
     TST     r2, #FastClock    ; don't do fast clock internal count here
     BNE     %FT20
     TST     r2, #InternalCount ; is it being inc'd by internal clock?
     BLNE    InternalTiming

; check active sensing status
; rx active sensing
20   TST     R2, #RxActiveSenseBit
     BEQ     %FT25    ; if active sensing is disabled

; get number of installed Ports
     MOV     r9, r2, LSR#NExtraPortsShift
     ANDS    r9, r9, #3  ; number of Ports - 1

; check active sensing
     LDR     r0, [r12, #RxInactiveTime] ; 4 counters
     ANDEQ   r0, r0, #&FF ; only 1 Port; clear other counters
     CMP     r9, #2
     BICLE   r0, r0, #&FF000000 ; at most 3 Ports; clear 4
     BICLT   r0, r0, #&FF0000   ; at most 2 Ports; clear 3

22   MOV     r1, r9, LSL#3 ; Port number X 8 for byte-shift
     MOV     r3, r0, LSR r1 ; move to lsb
     ANDS    r3, r3, #&FF  ; mask it
     MOV     r4, #1
     ADDNE   r0, r0, r4, LSL r1 ; add 1 if non-zero
     CMP     r3, #MaxRxInactivity     ; time expired?
     MOV     r4, #&FF
     BICGT   r0, r0, r4, LSL r1    ; clear byte if time expired
     BLGT    RxActiveSenseFail ;set error flag, generate midi error event, and turn off any midi notes
     SUBS    r9, r9, #1   ; Port number
     BPL     %BT22

     STR     r0, [r12, #RxInactiveTime]
     MOVS    r0, r0              ; is it 0?
     BICEQ   r2, r2, #RxActiveSenseBit  ; return to non-AS if all counters are 0
     STREQ   r2, [r12, #ModeFlagBits]   ; irq is off!

; tx active sensing
25   TST     r2, #TxActiveSenseBit
     BEQ     %FT30

; increment all non-zero counters
; (zero-byte means counter, and Active Sensing, in this channel is disabled)
; go round all installed Ports enabling transmit irqs if its active sensing time has expired

; get number of installed Ports
     MOV     r9, r2, LSR#NExtraPortsShift
     AND     r9, r9, #3  ; number of Ports - 1

     TEQP    pc, #3 + I_bit  ; disable interrupts
     LDR     r0, [R12, #TxInactiveTime] ; 4 counters

     MOV     r4, #1
27   MOV     r1, r9, LSL#3 ; Port number X 8 for byte-shift
     MOV     r3, r0, LSR r1 ; required byte to lsb
     ANDS    r3, r3, #&FF   ; mask it
     ADDNE   r0, r0, r4, LSL r1 ; add 1 if non-zero
     CMP     r3, #MaxTxInactivity     ; time expired?
     BLGE    EnableTxIRQ
     SUBS    r9, r9, #1   ; Port number
     BPL     %BT27

     STR     r0, [r12, #TxInactiveTime]
     MOVS    r0, r0              ; is it 0?
     BICEQ   r2, r2, #TxActiveSenseBit  ; return to non-AS if all counters are 0
     STREQ   r2, [r12, #ModeFlagBits]   ; irq is off!

30   TST     r2, #SoundEnableBit
     BLNE    SoundInterpreter  ; if sound enabled, interpret data
;nb this corrupts regs r0-r2
; exit
40   LDMFD   sp!, {lr}            ; get old lr_svc value
     TEQP    pc, #I_bit:OR:2      ; change back to irq mode
     MOVNV   r0, r0               ; regbank delay
     LDMFD   sp!, {r0-r4,r9, pc}^

SoundSWIError
; If a sound swi causes an error in the centisecond routine then attempt to disable SIRQ,
; find which sound module caused the error and mark it as not present
     STMFD   sp!, {r0-r4,lr}
     MOV     r0, #0
     MOV     r1, #0
     MOV     r2, #0
     MOV     r3, #0
     LDR     r4, [r12, #Sound_QTick]
     TEQP    pc, #I_bit:OR:3
     SWI     XSound_Configure       ; disconnect centisecond routine
     LDR     r2, [r12, #ModeFlagBits]
     BICVS   r2, r2, #Sound0Present
     MOV     r0, #0
     MOV     r1, #0
     SWI     XSound_InstallVoice    ; dummy call to sound1
     BICVS   r2, r2, #Sound1Present
     MOV     r0, #0
     SWI     XSound_QBeats          ; dummy call to sound2
     BICVS   r2, r2, #Sound2Present
     STR     r2, [r12, #ModeFlagBits]
     LDMFD   sp!, {r0-r4,pc}^      ; preserve old flags

RxActiveSenseFail
; Active Sensing reception has stopped
; r9 is Port number (0..3)
     STMFD   sp!, {r0-r1,r3, lr}
; set the error flag
     MOV     r1, #"A"
     ADD     r3, r12, r9
     STRB    r1, [r3, #ErrorFlag]
; generate a midi error event
     MOV     r0, #Event_MIDI
     MOV     r1, #MIDI_ErrorEvent
     SWI     XOS_GenerateEvent
; and turn off any midi notes
     TST     r2, #SoundEnableBit
     BLNE    DoAllNotesOff
     LDMFD   sp!, {r0-r1,r3, pc}^

InternalTiming ROUT
; Internal Timing Mode is set (not FastClock mode)
;can read tempo to check for extreme setting of &FFFF =16 beats/bar and
; ensure increment every centisecond. If possible use beat counter
; (tricky!) because this should ensure bar alignment.
; The bar count is updated on the bar event.
; enter with r0 = current value of BEAT
; r2 = mode flags
; r12 = wp
     STMFD   sp!, {r0-r1, r3-r5,lr}
     TEQP    pc, #3    ; enable interrupts
     LDR     r1, [r12, #Tempo]
; r1 = tempo
     CMP     r1, #&FF00    ; =16 beats per centisecond = maximum tempo
     BGT     TCtimeUp ;if tempo set to max, then send a TC every centisecond
     LDR     r5, [r12, #LastBeat]
     LDR     r3, [r12, #BarLength]
; r5 = last beat
; r3 = bar length
     CMP     r3, #1  ; if the bar count is 0 or 1 then use tempo value
     BGT     %FT10

; send a tick every n centiseconds
     ADD     r5, r5, r1  ; increment counter by tempo = microbeat/cs * &1000
     CMP     r5, #BeatCount:SHL:12 ; 16 whole microbeats passed?
     MOVGE   r5, #0
     STR     r5, [r12, #LastBeat]    ; new value
     BGE     TCtimeUp
     LDMFD   sp!, {r0-r1,r3-r5, pc}^    ; return

10   MOV     r4, r5, LSR#16        ; incremental count in b31-b16 
; r4 = incremental beats
     MOV     r5, r5, LSL#16    ; clear top 16 bits
     MOV     r5, r5, LSR#16    ; old value in b15-b0
     CMP     r5, r0           ; compare with current beat
     BNE     NewBeatValue
     CMP     r1, #TempoUnit    ; this value is 1 tempo beat/centisecond
     LDMLTFD sp!, {r0-r1,r3-r5, pc}^  ; return

NewBeatValue      ; count beats elapsed since last TC sent
     SUBS    r1, r0, r5      ; new beats-old beats, and check for bar wrap
     ADDMI   r1, r1, r3      ; add a bar of beats
     ADD     r4, r4, r1      ; add current beats to incrementing value
     CMP     r4, #BeatCount  ; =16
     MOV     r5, r0          ; new beat value
     ORRLT   r5, r5, r4, LSL#16  ; update top 16b
     STR     r5, [r12, #LastBeat]    ; new value
     LDMLTFD sp!, {r0-r1,r3-r5, pc}^ ;return

TCtimeUp
     TEQP    PC, #I_bit:OR:3    ; disable interrupts for consistent flags
     LDR     r2, [r12, #ModeFlagBits]
     TST     r2, #SendRT ; test for an existing sendrt request
     ORREQ   r2, r2, #SendTC  ; instruct irq routine to send a Timing Clock msg
     STR     r2, [r12, #ModeFlagBits]
     MOV     r1, r2, LSR#NExtraPortsShift
     AND     r1, r1, #3  ; number of Ports installed - 1
; transmit a tc from all Ports
20   ADD     r3, r12, r1, LSL#2  ; x 4
     LDR     r0, [r3, #UARTbase] ; uart/acia base address
; r0 = device base address
     TST     r0, #PortTypeTestBit
     MOVEQ   r5, #ACIATxIRQEnable
     STREQB  r5, [r0]             ; enable TDRE interrupts
     MOVNE   r5, #RxRDYirq:OR:TxRDYirq
     STRNEB  r5, [r0, #IROffset]  ; enable TxRDY interrupts
     ADD     r3, r12, r1
     STRB    r5, [r3, #IMRCurrent]
     SUBS    r1, r1, #1
     BPL     %BT20      ; repeat for each installed Port
     LDMFD   sp!, {r0-r1,r3-r5, pc}^ ; return

DoRareUpdate ROUT; Collect here swis which don't have to happen every centisec.
 ; enter with r2=mode flags                       SWIs are expensive!
     STMFD   sp!, {r0-r1, lr}
     MOV     r0, #-1        ; get max count of sound beat counter
     SWI     XSound_QBeats
     LDMVSFD sp!, {r0-r1, pc}  ; return with v-set if error.
     LDR     r1, [r12, #BarLength]  ; to look for a bar length change
     CMP     r0, r1          ; has value of beats/bar changed?
     STRNE   r0, [r12, #BarLength] ; if so, store new value
     MOV     r0, #0
     STRNE   r0, [r12, #CurrentQBeat] ; and clear beat and bar counters
     SWI     XSound_QTempo
     LDMVSFD sp!, {r0-r1, pc}  ; return with v-set if error.
     STR     r0, [r12, #Tempo]      ; update tempo setting
     LDMFD   sp!, {r0-r1, pc}^

  END
